﻿using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;

namespace Editor.Common
{
    public struct ClassInfo
    {
        public const string Public = "public";
        public const string Private = "private";
        public const string Protected = "protected";
        public const string ReadOnly = "readonly";
        public const string Override = "override";
        
        public string Path { get; set; }
        public string FileName { get; set; }
        public string[] Modifier { get; set; }
        public string ClassName { get; set; }
        public bool IsStruct { get; set; }
        public string Namespace { get; set; }
        public string[] Using { get; set; }
        public string[] ParentClass { get; set; }
        public FieldInfo[] Fields { get; set; }
        public FunctionInfo[] Functions { get; set; }

        public static ClassInfo Dummy { get; } = new ClassInfo() { };
        
        public static bool operator ==(ClassInfo a, ClassInfo b)
        {
            return a.Equals(b);
        }
        
        public static bool operator !=(ClassInfo a, ClassInfo b)
        {
            return !(a == b);
        }
        
        private bool Equals(ClassInfo other)
        {
            return Path == other.Path && FileName == other.FileName && ClassName == other.ClassName && Namespace == other.Namespace && ParentClass == other.ParentClass && Equals(Fields, other.Fields) && Equals(Functions, other.Functions);
        }
        
        public override bool Equals(object obj)
        {
            return obj is ClassInfo other && Equals(other);
        }
        
        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = (Path != null ? Path.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (FileName != null ? FileName.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (ClassName != null ? ClassName.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Namespace != null ? Namespace.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (ParentClass != null ? ParentClass.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Fields != null ? Fields.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Functions != null ? Functions.GetHashCode() : 0);
                return hashCode;
            }
        }
    }
    
    public struct FieldInfo
    {
        public string[] Modifier{ get; set; }
        public string Type{ get; set; }
        public string FieldName{ get; set; }
        public string getter { get; set; }
        public string setter { get; set; }
        
        public static FieldInfo Dummy { get; } = new FieldInfo() { };
        
        public static bool operator ==(FieldInfo a, FieldInfo b)
        {
            return a.Equals(b);
        }
        
        public static bool operator !=(FieldInfo a, FieldInfo b)
        {
            return !(a == b);
        }
        
        private bool Equals(FieldInfo other)
        {
            return Modifier == other.Modifier && Type == other.Type && FieldName == other.FieldName;
        }
        
        public override bool Equals(object obj)
        {
            return obj is FieldInfo other && Equals(other);
        }
        
        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = (Modifier != null ? Modifier.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Type != null ? Type.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (FieldName != null ? FieldName.GetHashCode() : 0);
                return hashCode;
            }
        }
    }

    public struct FunctionInfo
    {
        public string[] Modifier { get; set; }
        public string ReturnType { get; set; }
        public string FunctionName { get; set; }
        public ParameterInfo[] Parameters { get; set; }
        public string FunctionBody { get; set; }
        

        public static FunctionInfo Dummy { get; } = new FunctionInfo() { };
        
        public static bool operator ==(FunctionInfo a, FunctionInfo b)
        {
            return a.Equals(b);
        }
        
        public static bool operator !=(FunctionInfo a, FunctionInfo b)
        {
            return !(a == b);
        }
        
        private bool Equals(FunctionInfo other)
        {
            return Modifier == other.Modifier && ReturnType == other.ReturnType && FunctionName == other.FunctionName && Equals(Parameters, other.Parameters) && FunctionBody == other.FunctionBody;
        }
        
        public override bool Equals(object obj)
        {
            return obj is FunctionInfo other && Equals(other);
        }
        
        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = (Modifier != null ? Modifier.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (ReturnType != null ? ReturnType.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (FunctionName != null ? FunctionName.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Parameters != null ? Parameters.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (FunctionBody != null ? FunctionBody.GetHashCode() : 0);
                return hashCode;
            }
        }
    }
    
    public struct ParameterInfo
    {
        public string Type { get; set; }
        public string Name { get; set; }
        
        public ParameterInfo(string type, string name)
        {
            Type = type;
            Name = name;
        }
        
        public static ParameterInfo Dummy { get; } = new ParameterInfo() { };
        
        public static bool operator ==(ParameterInfo a, ParameterInfo b)
        {
            return a.Equals(b);
        }
        
        public static bool operator !=(ParameterInfo a, ParameterInfo b)
        {
            return !(a == b);
        }

        private bool Equals(ParameterInfo other)
        {
            return Type == other.Type && Name == other.Name;
        }

        public override bool Equals(object obj)
        {
            return obj is ParameterInfo other && Equals(other);
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Type, Name);
        }
    }

    public static class ClassGenerator
    {
        public static void Generate( ClassInfo classInfo)
        {
            if (classInfo == ClassInfo.Dummy)
            {
                return;
            }

            var sb = new StringBuilder();
            sb.AppendLine(GenerateUsing(classInfo));
            sb.Append(GenerateNamespace(classInfo));
            sb.Append(GenerateClass(classInfo));
            string fileFolderPath = Path.Combine(Application.dataPath, classInfo.Path);
            if (!Directory.Exists(fileFolderPath))
            {
                Directory.CreateDirectory(fileFolderPath);
            }

            string filePath = Path.Combine(fileFolderPath, classInfo.FileName);
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
            File.WriteAllText(filePath, sb.ToString());
            AssetDatabase.Refresh();
            Debug.Log($"Generate class: {classInfo.ClassName} at {Path.Combine(classInfo.Path, classInfo.FileName)}");
        }
        
        private static string GenerateUsing(ClassInfo classInfo)
        {
            if (classInfo.Using == null || classInfo.Using.Length == 0)
            {
                return string.Empty;
            }
            return $"using {string.Join(";\n", classInfo.Using)};\n";
        }

        private static string GenerateNamespace(ClassInfo classInfo)
        {
            if (string.IsNullOrEmpty(classInfo.Namespace))
            {
                return string.Empty;
            }
            return $"namespace {classInfo.Namespace}\n{{\n";
        }

        private static string GenerateClass(ClassInfo classInfo)
        {
            var sb = new StringBuilder();
            var defineStr = $"    {string.Join(" ",classInfo.Modifier)} {(classInfo.IsStruct ? "struct" : "class")} {classInfo.ClassName}";
            if (classInfo.ParentClass != null && classInfo.ParentClass.Length > 0)
            {
                defineStr += $" : {string.Join(',', classInfo.ParentClass)}";
            }
            sb.AppendLine(defineStr); // class definition
            sb.AppendLine("    {");  // class start
            sb.Append(GenerateFields(classInfo));
            sb.AppendLine("");
            sb.Append(GenerateFunctions(classInfo));
            sb.AppendLine("    }"); // class end
            if (!string.IsNullOrEmpty(classInfo.Namespace))
            {
                sb.AppendLine("}"); // namespace end
            }
            return sb.ToString();
        }

        private static string GenerateFields(ClassInfo classInfo)
        {
            var sb = new StringBuilder();
            foreach (var field in classInfo.Fields)
            {
                Debug.Assert(field.Type != null, "field.Type cannot be null");
                Debug.Assert(field.FieldName != null, "field.FieldName cannot be null");
                string fieldDefine = $"        {(field.Modifier != null ? string.Join(" ", field.Modifier) : "")} {field.Type} {field.FieldName}";
                if (string.IsNullOrEmpty(field.getter) && string.IsNullOrEmpty(field.setter))
                {
                    fieldDefine += ";";
                }
                else
                {
                    fieldDefine += $" {{ {(!string.IsNullOrEmpty(field.getter) ? field.getter + ";" : string.Empty)} {(!string.IsNullOrEmpty(field.setter) ? field.setter + ";" : string.Empty)} }}";
                }
                sb.AppendLine(fieldDefine);
            }
            return sb.ToString();
        }

        private static string GenerateFunctions(ClassInfo classInfo)
        {
            var sb = new StringBuilder();
            foreach (var function in classInfo.Functions)
            {
                Debug.Assert(function.ReturnType != null, "function.ReturnType cannot be null");
                Debug.Assert(function.FunctionName != null, "function.FunctionName cannot be null");
                sb.AppendLine($"        {string.Join(" ", function.Modifier)} {function.ReturnType} {function.FunctionName}({string.Join(", ", function.Parameters.Select(p => $"{p.Type} {p.Name}"))})");
                sb.AppendLine("        {");
                sb.AppendLine($"{function.FunctionBody}");
                sb.AppendLine("        }");
                sb.AppendLine();
            }
            return sb.ToString();
        }
    }
}